package cn.bbstone.pisces2.comm.codec;

import org.apache.commons.beanutils.BeanUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import cn.bbstone.pisces2.comm.BFileCmd;
import cn.bbstone.pisces2.comm.Const;
import cn.bbstone.pisces2.proto.rsp.RspData;
import cn.bbstone.pisces2.proto.rsp.RspFile;
import cn.bbstone.pisces2.proto.rsp.RspFileData;
import cn.bbstone.pisces2.proto.rsp.RspFileFile;
import cn.bbstone.pisces2.proto.rsp.RspListData;
import cn.bbstone.pisces2.proto.rsp.RspListFile;
import cn.bbstone.pisces2.util.BByteUtil;
import cn.bbstone.pisces2.util.CipherUtil;
import io.netty.buffer.ByteBuf;

public class RspFileDecoder {
	private static Logger log = LoggerFactory.getLogger(RspFileDecoder.class);
	
    public RspFileDecoder() {
    }


    public RspFile decode(ByteBuf byteBuf) throws Exception {
        // magic + msgType
        if (byteBuf.readableBytes() < 14) return null;
        // msg not start with BBSTONE, will be abandon
        byte[] magicBytes = new byte[7];
        byteBuf.getBytes(0, magicBytes);
        String magic = BByteUtil.toStr(magicBytes);
        if (!Const.magic.equals(magic)) return null;
        // get msgType
        Byte msgType = byteBuf.getByte(7);

        if (BFileCmd.RSP_LIST_INFO == msgType || BFileCmd.RSP_FILE_INFO == msgType) {
            if (byteBuf.readableBytes() < Const.HEAD_LEN_RSP_INFO) return null;
            // check if request file info
            RspFile rspFile = parseInfoMsg(byteBuf);
            switch (msgType) {
                case 0x03:
                    RspListFile rspListInfo = new RspListFile();
                    BeanUtils.copyProperties(rspListInfo, rspFile);
//                    list.add(rspListInfo);
                    return rspListInfo;
//                    break;
                case 0x05:
                    RspFileFile rspFileInfo = new RspFileFile();
                    BeanUtils.copyProperties(rspFileInfo, rspFile);
//                    list.add(rspFileInfo);
                    return rspFileInfo;
//                    break;
                default:

            }
        }
        // check if request file data
        if (BFileCmd.RSP_LIST_DATA == msgType || BFileCmd.RSP_FILE_DATA == msgType) {
            if (byteBuf.readableBytes() < Const.HEAD_LEN_RSP_DATA) return null;
            RspData rspData = parseDataMsg(byteBuf);
            switch (msgType) {
                case 0x04:
                    RspListData rspListData = new RspListData();
                    BeanUtils.copyProperties(rspListData, rspData);
//                    list.add(rspListData);
                    return rspListData;
//                    break;
                case 0x06:
                    RspFileData rspFileData = new RspFileData();
                    BeanUtils.copyProperties(rspFileData, rspData);
//                    list.add(rspFileData);
                    return rspFileData;
//                    break;
                default:
            }
        }
        return null;
    }

    private RspFile parseInfoMsg(ByteBuf msg) {
        // skip magic
        msg.skipBytes(7);
        // msgType
        Byte msgType = msg.readByte();
        // msgId
        byte[] msgIdBytes = new byte[32];
        msg.readBytes(msgIdBytes);
        String msgId = BByteUtil.toStr(msgIdBytes);
        // reqTs
        long reqTs = msg.readLong();
        // rspTs
        long rspTs = msg.readLong();
        // fileNo
        long fileNo = msg.readLong();
        // fileSize
        long fileSize = msg.readLong();
        // chunkNo
        int chunkNo = msg.readInt();
        // chunks
        int chunks = msg.readInt();
        // body checksum
        byte[] checksumBytes = new byte[32];
        msg.readBytes(checksumBytes);
        String checksum = BByteUtil.toStr(checksumBytes);
        // bodyLen
        int bodyLen = msg.readInt();
        byte[] bodyBytes = new byte[bodyLen];
        msg.readBytes(bodyBytes);
        String bodyData = BByteUtil.toStr(bodyBytes); // value is file's checksum
        if (!checksum.equals(CipherUtil.md5(bodyData))) {
            log.error("recv file info error, msgType: {}, fileNo: {}, expected body checksum: {}, but it is: {}",
                    msgType, fileNo, checksum, bodyData);
            throw new RuntimeException("recv file info error.");
        }
        // build model
        RspFile rspFile = new RspFile();
        rspFile.setMsgType(msgType);
        rspFile.setMsgId(msgId);
        rspFile.setReqTs(reqTs);
        rspFile.setRspTs(rspTs);
        rspFile.setFileNo(fileNo);
        rspFile.setFileSize(fileSize);
        rspFile.setChunkNo(chunkNo);
        rspFile.setChunks(chunks);
        rspFile.setChecksum(checksum);
        rspFile.setBodyLen(bodyLen);
        rspFile.setBodyData(bodyBytes);
        return rspFile;
    }


    private RspData parseDataMsg(ByteBuf msg) {
        // skip magic
        msg.skipBytes(7);
        // msgType
        Byte msgType = msg.readByte();
        // msgId
        byte[] msgIdBytes = new byte[32];
        msg.readBytes(msgIdBytes);
        String msgId = BByteUtil.toStr(msgIdBytes);
        // reqTs
        long reqTs = msg.readLong();
        // rspTs
        long rspTs = msg.readLong();
        // fileNo
        long fileNo = msg.readLong();
        // fileSize
        long fileSize = msg.readLong();
        // chunkNo
        int chunkNo = msg.readInt();
        // chunks
        int chunks = msg.readInt();
        // body checksum
        byte[] checksumBytes = new byte[32];
        msg.readBytes(checksumBytes);
        String checksum = BByteUtil.toStr(checksumBytes);
        // bodyLen
        int bodyLen = msg.readInt();
        // body data
        byte[] bodyBytes = new byte[bodyLen];
        msg.readBytes(bodyBytes);
//        String bodyData = BByteUtil.toStr(bodyBytes); // value is file's checksum
        // validate body data with body checksum
        if (bodyLen > 0) { // only not empty file will check body checksum
            String bodyChecksum = CipherUtil.md5(bodyBytes);
            if (!checksum.equals(bodyChecksum)) {
                log.error("recv file data error, msgType: {}, chunkNo: {}, expected body checksum: {}, but it is: {}",
                        msgType, chunkNo, checksum, bodyChecksum);
                throw new RuntimeException("recv file info error.");
            }
        }// else {// empty file with no data(no body), so has no body checksum(value is null)


        // build model
        RspData rspData = new RspData();
        rspData.setMsgType(msgType);
        rspData.setMsgId(msgId);
        rspData.setReqTs(reqTs);
        rspData.setRspTs(rspTs);
        rspData.setFileNo(fileNo);
        rspData.setFileSize(fileSize);
        rspData.setChunkNo(chunkNo);
        rspData.setChunks(chunks);
        rspData.setChecksum(checksum);
        rspData.setBodyLen(bodyLen);
        rspData.setBodyData(bodyBytes);
        //
        return rspData;
    }
}
